/* 
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

using DCSharp.Hashing;
using DCSharp.Logging;
using DCSharp.Settings;
using DCSharp.Xml;

namespace DCSharp.Backend.Managers
{
	public delegate void HashHandler(FileNode file, FileInfo fileInfo);

	public class DirectoryLoadedEventArgs : EventArgs
	{
		private string path;
		private DirectoryNode directory;

		public DirectoryLoadedEventArgs(string path, DirectoryNode directory)
		{
			this.path = path;
			this.directory = directory;
		}

		public string Path
		{
			get { return path; }
		}

		public DirectoryNode Directory
		{
			get { return directory; }
		}
	}

	/// <summary>
	/// Updates a <see cref="DirectoryNode"/> to match a directory on the file
	/// system.
	/// </summary>
	public class ShareLoader
	{
		private static Logger log = LogManager.GetLogger("ShareManager");

		public event EventHandler<DirectoryLoadedEventArgs> DirectoryLoaded;

		private IRuntimeSettings settings;
		private HashStore hashStore;
		private HashHandler hash;

		private ActionQueue<string> actionQueue;

		public ShareLoader(IRuntimeSettings settings, HashStore hashStore,
			HashHandler hash)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (hashStore == null)
			{
				throw new ArgumentNullException("hashStore");
			}
			if (hash == null)
			{
				throw new ArgumentNullException("hash");
			}
			this.settings = settings;
			this.hashStore = hashStore;
			this.hash = hash;

			actionQueue = new ActionQueue<string>("ShareLoader Thread");
		}

		#region Properties

		public bool IsLoading
		{
			get { return actionQueue.IsRunning; }
		}

		#endregion

		#region Methods

		public void LoadDirectory(string path, DirectoryNode directory)
		{
			if (path == null)
			{
				throw new ArgumentNullException("path");
			}
			if (directory == null)
			{
				throw new ArgumentNullException("directory");
			}
			actionQueue.Queue(path, delegate
			{
				Start(path, directory);
			});
		}

		public void Abort(string path)
		{
			actionQueue.Abort(path);
		}

		protected virtual void OnDirectoryLoaded(string path, DirectoryNode directory)
		{
			if (DirectoryLoaded != null)
			{
				DirectoryLoaded(this, new DirectoryLoadedEventArgs(path, directory));
			}
		}

		private void Start(string path, DirectoryNode directory)
		{
			TryLoadDirectory(directory, path, actionQueue.AbortEvent);
			if (!actionQueue.AbortEvent.WaitOne(0, false))
			{
				OnDirectoryLoaded(path, directory);
			}
		}

		private void TryLoadDirectory(DirectoryNode directory, string path,
			ManualResetEvent abortEvent)
		{
			DirectoryInfo dirInfo = TryGetDirectoryInfo(path);
			if (dirInfo != null && dirInfo.Exists)
			{
				try
				{
					LoadDirectory(directory, dirInfo, settings.ShareHidden,
						abortEvent);
				}
				catch (Exception e)
				{
					log.Error("Loading directory failed", e);
				}
			}
			else
			{
				directory.Directories = new List<DirectoryNode>();
				directory.Files = new List<FileNode>();
			}
		}

		private void LoadDirectory(DirectoryNode directory, DirectoryInfo dirInfo,
			bool addHidden, ManualResetEvent abortEvent)
		{
			// Load the directories
			foreach (DirectoryInfo childDirInfo in dirInfo.GetDirectories())
			{
				if (abortEvent.WaitOne(0, false))
				{
					return;
				}
				bool dirIsHidden = childDirInfo.Attributes ==
					(childDirInfo.Attributes | FileAttributes.Hidden);

				if (addHidden || !dirIsHidden)
				{
					DirectoryNode childDir = new DirectoryNode(childDirInfo.Name);
					directory.Directories.Add(childDir);

					try
					{
						LoadDirectory(childDir, childDirInfo, addHidden,
							abortEvent);
					}
					catch (Exception e)
					{
						log.Error("Loading directory failed", e);
					}
				}
			}

			// Load the files
			foreach (FileInfo fileInfo in dirInfo.GetFiles())
			{
				if (abortEvent.WaitOne(0, false))
				{
					return;
				}
				bool fileIsHidden = fileInfo.Attributes ==
					(fileInfo.Attributes | FileAttributes.Hidden);

				if (addHidden || !fileIsHidden)
				{
					FileNode file = new FileNode(fileInfo.Name, fileInfo.Length, null);
					directory.Files.Add(file);

					lock (hashStore)
					{
						if (!hashStore.HasHash(fileInfo))
						{
							hash(file, fileInfo);
						}
						else if (file.TTH == null)
						{
							file.TTH = hashStore.GetRootHash(fileInfo);
						}
					}
				}
			}
		}

		private static DirectoryInfo TryGetDirectoryInfo(string path)
		{
			try
			{
				return new DirectoryInfo(path);
			}
			catch
			{
				return null;
			}
		}

		#endregion
	}
}
